using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.IO;
using System.Text;

namespace Code_Generator
{
	public class ModelBuilder : Builder
	{
		public ModelBuilder() { }
		public ModelBuilder(Setup setup) : base(setup) { }


		public int GetCurrentRowCount()
		{
			using (var con = Setup.OpenConnection())
			{
				using (var cmd = new SqlCommand("SELECT COUNT(*) FROM " + Setup.TableName, con))
				{
					return (int)cmd.ExecuteScalar();
				}
			}
		}


		public void CreateModel()
		{
			Setup.PopulateMetaData();

			var template = LoadTemplate("Classes", "Model");

			var properties = new List<string>();

			foreach (var column in MetaData.Columns)
			{
				if (column.IsIdentity || column.IsAuditField)
					properties.Add(BuildReadOnlyProperty(column));
				else
					properties.Add(BuildProperty(column));
			}

			template.Replace("$PrimaryKey-Name$", MetaData.PrimaryColumn.SqlName);
			template.Replace("$Properties$", properties);
			template.Replace("$ToString$", BuildToString());

			var targetFilePath = Path.Combine(Setup.TargetFolder, Setup.ClassName + "Model.cs");
			File.WriteAllText(targetFilePath, template.ToString());

		}
		public void CreateRepository()
		{
			Setup.PopulateMetaData();
			var template = LoadTemplate("Classes", "Repository");


			template.Replace("$ParameterName$", Setup.ParameterName);

			template.Replace("$PrimaryKey-Name$", MetaData.PrimaryColumn.SqlName);
			template.Replace("$PrimaryKey-Parameter$", MetaData.PrimaryColumn.ClrParameterName);
			template.Replace("$PrimaryKey-Type$", MetaData.PrimaryColumn.ClrType);

			var targetFilePath = Path.Combine(Setup.TargetFolder, Setup.ClassName + "Repository.cs");
			File.WriteAllText(targetFilePath, template.ToString());

		}

		public void CreateCollection()
		{
			Setup.PopulateMetaData();
			var template = LoadTemplate("Classes", "Collection");


			template.Replace("$ParameterName$", Setup.ParameterName);

			template.Replace("$PrimaryKey-Name$", MetaData.PrimaryColumn.SqlName);
			template.Replace("$PrimaryKey-Parameter$", MetaData.PrimaryColumn.ClrParameterName);
			template.Replace("$PrimaryKey-Property$", MetaData.PrimaryColumn.ClrName);
			template.Replace("$PrimaryKey-Type$", MetaData.PrimaryColumn.ClrTypeNonNullable);

			var targetFilePath = Path.Combine(Setup.TargetFolder, Setup.ClassName + "Collection.cs");
			File.WriteAllText(targetFilePath, template.ToString());

		}


		public void CreateToken()
		{
			Setup.PopulateMetaData();

			var template = LoadTemplate("Classes", "Token");


			var columnList = new List<DBColumn>();
			columnList.Add(MetaData.PrimaryColumn);
			foreach (var tokenColumn in MetaData.Columns.Where(c => c.IsTokenColumn && c != MetaData.PrimaryColumn))
			{
				columnList.Add(tokenColumn);
			}

			var properties = new List<string>();
			var fields = new List<string>();
			var constructorFields = new List<string>();

			properties.Add(BuildSimpleProperty(MetaData.PrimaryColumn, "ReadOnlyProperty", false, false, "PrimaryKey"));

			foreach (var column in columnList)
			{
				fields.Add(BuildSimpleProperty(column, "ReadOnlyField", false, false));
				properties.Add(BuildSimpleProperty(column, "ReadOnlyProperty", false, false));
				constructorFields.Add(BuildSimpleProperty(column, "Constructor-Field", false, false));
			}

			template.Replace("$Fields$", fields);
			template.Replace("$Fields$", "");
			template.Replace("$Properties$", properties);
			template.Replace("$Constructor-Fields$", constructorFields);
			template.Replace("$ToString$", BuildToString());


			var targetFilePath = Path.Combine(Setup.TargetFolder, Setup.ClassName + "Token.cs");
			File.WriteAllText(targetFilePath, template.ToString());
		}


		string BuildReadOnlyProperty(DBColumn column, string propertyName = null)
		{
			StringBuilder template;
			if (!column.IsString)
			{
				template = LoadTemplate("Model-Properties", "ReadOnlyProperty1");
			}
			else
			{
				template = LoadTemplate("Model-Properties", "ReadOnlyProperty2");
				template.Replace("$Field-TypeDefault$", "\"\"");
			}

			var attributes = new List<string>();

			if (column.IsPrimaryKey)
				attributes.Add("\t\t[Action(\"Select\")]");

			if (column.IsPrimaryKey)
				attributes.Add("\t\t[Action(\"Delete\")]");

			template.Replace("$Field-Attributes$", attributes);
			template.Replace("$Field-Type$", column.ClrType);
			template.Replace("$Field-Name$", column.ClrName);
			template.Replace("$Property-Name$", propertyName ?? column.ClrName);

			return template.ToString();
		}

		private string BuildToString()
		{
			if (String.IsNullOrWhiteSpace(Setup.ToStringExpression))
				return null;

			var template = LoadTemplate("Methods", "ToString");
			template.Replace("$ToString-Expression$", Setup.ToStringExpression);

			return template.ToString();
		}

		string BuildProperty(DBColumn column, string propertyName = null)
		{
			StringBuilder template;
			if (!column.IsString)
			{
				template = LoadTemplate("Model-Properties", "Property1");
			}
			else
			{
				template = LoadTemplate("Model-Properties", "Property2");
				template.Replace("$Field-TypeDefault$", "\"\"");
			}

			var attributes = new List<string>();

			if (column.IsString && column.Length > 0)
				attributes.Add("\t\t[StringLength(" + column.Length + ")]");

			if (column.IsRequired || (column.IsString && !column.IsNullable) || column.IsCreatedBy)
				attributes.Add("\t\t[Required]");

			if (!column.IsIdentity && !column.IsCreatedDate && !column.IsUpdatedBy && !column.IsUpdatedDate)
				attributes.Add("\t\t[Action(\"Insert\")]");

			if (!column.IsCreatedDate && !column.IsCreatedBy)
				attributes.Add("\t\t[Action(\"Update\")]");

			if (column.IsPrimaryKey)
				attributes.Add("\t\t[Action(\"Delete\")]");

			if (column.IsString && column.Length == -1)
				attributes.Add("\t\t[DataType(DataType.MultilineText)]");
				

			template.Replace("$Field-Attributes$", attributes.OrderBy(s => s).ToList());
			template.Replace("$Field-Type$", column.ClrType);
			template.Replace("$Field-Name$", column.ClrName);
			template.Replace("$Property-Name$", propertyName ?? column.ClrName);

			return template.ToString();
		}

		internal void CreateEnum()
		{
			Setup.PopulateMetaData();


			var template = LoadTemplate("Enum", "Enum");

			var enumValues = new List<string>();
			var descriptions = new List<string>();

			var dt = Setup.ExecuteTable("SELECT {0}, {1} FROM {2} ORDER BY {0}", MetaData.PrimaryColumn.SqlName, MetaData.EnumDescriptionColumn.SqlName, Setup.TableName);
			foreach (DataRow row in dt.Rows)
			{
				var fragment = LoadTemplate("Enum", "EnumMember");
				fragment.ReplaceEncoded("$EnumDescription$", row[MetaData.EnumDescriptionColumn.SqlName]);
				fragment.ReplaceClrAlias("$EnumName$", row[MetaData.EnumDescriptionColumn.SqlName]);
				fragment.Replace("$EnumValue$", row[MetaData.PrimaryColumn.SqlName]);
				enumValues.Add(fragment.ToString());

				var fragment2 = LoadTemplate("Enum", "EnumToDescription");
				fragment2.ReplaceEncoded("$EnumDescription$", row[MetaData.EnumDescriptionColumn.SqlName]);
				fragment2.ReplaceClrAlias("$EnumName$", row[MetaData.EnumDescriptionColumn.SqlName]);
				descriptions.Add(fragment2.ToString());
			}


			template.Replace("$Enum-Values$", enumValues);
			template.Replace("$Enum-ToDescription-Values$", descriptions);


			var targetFilePath = Path.Combine(Setup.TargetFolder, Setup.ClassName + ".cs");
			File.WriteAllText(targetFilePath, template.ToString());
		}

		protected override StringBuilder LoadTemplate(string section, string file)
		{
			StringBuilder template = base.LoadTemplate(section, file);
			template.Replace("$Tool-Version$", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString());
			template.ReplaceEncoded("$Tool-Name$", "Granite Code Generator");
			template.Replace("$Namespace$", Setup.Namespace);
			template.Replace("$ClassName$", Setup.ClassName);

			template.ReplaceEncoded("$DataContract-Namespace$", Setup.DataContractNamespace);
			template.ReplaceEncoded("$DataContract-Name$", Setup.ClassName);

			template.Replace("$SchemaName$", Setup.SchemaName);
			template.Replace("$TableName$", Setup.TableName);

			return template;
		}

		private string BuildSimpleProperty(DBColumn column, string templateName, bool isDataMember, bool useActionTags, string propertyName = null)
		{
			StringBuilder template = LoadTemplate("Simple-Properties", templateName);

			var fieldAttributes = new List<string>();
			var propertyAttributes = new List<string>();

			if (isDataMember)
				propertyAttributes.Add("\t\t[DataMember]");

			if (useActionTags)
			{
				if (column.IsPrimaryKey)
					propertyAttributes.Add("\t\t[Action(\"Select\")]");

				if (!column.IsIdentity && !column.IsCreatedDate && !column.IsUpdatedBy && !column.IsUpdatedDate)
					propertyAttributes.Add("\t\t[Action(\"Insert\")]");

				if (!column.IsCreatedDate && !column.IsCreatedBy)
					propertyAttributes.Add("\t\t[Action(\"Update\")]");

				if (column.IsPrimaryKey)
					propertyAttributes.Add("\t\t[Action(\"Delete\")]");
			}

			template.Replace("$Field-Attributes$", fieldAttributes);
			template.Replace("$Property-Attributes$", propertyAttributes);
			template.Replace("$Field-Type$", column.ClrType);
			template.Replace("$Field-Name$", "m_" + column.ClrName);
			template.Replace("$Property-Name$", propertyName ?? column.ClrName);

			return template.ToString();
		}

		public void CreateDTO()
		{
			Setup.PopulateMetaData();

			var template = LoadTemplate("Classes", "DTO");

			var properties = new List<string>();
			//var fields = new List<string>();
			var constructorFields = new List<string>();

			foreach (var column in MetaData.Columns)
			{
				//fields.Add(BuildSimpleProperty(column, "Field"));
				properties.Add(BuildSimpleProperty(column, "AutoProperty", true, true));
				constructorFields.Add(BuildSimpleProperty(column, "Constructor-Property", true, true));
			}

			//template.Replace("$Fields$", fields);
			template.Replace("$Fields$", "");
			template.Replace("$Properties$", properties);
			template.Replace("$Constructor-Fields$", constructorFields);
			template.Replace("$ToString$", BuildToString());

			var targetFilePath = Path.Combine(Setup.TargetFolder, Setup.ClassName + "DTO.cs");
			File.WriteAllText(targetFilePath, template.ToString());
		}
	}
}
